package com.hqd.jjvm.jnative.lang;

import com.hqd.jjvm.jnative.JNativeRegistry;
import com.hqd.jjvm.rtda.JThread;
import com.hqd.jjvm.rtda.LocalVars;
import com.hqd.jjvm.rtda.StackFrame;
import com.hqd.jjvm.rtda.heap.JClass;
import com.hqd.jjvm.rtda.heap.JField;
import com.hqd.jjvm.rtda.heap.JMethod;
import com.hqd.jjvm.rtda.heap.classloader.JClassLoader;
import com.hqd.jjvm.rtda.heap.ref.JMethodRef;
import com.hqd.jjvm.rtda.heap.state.JClassState;
import com.hqd.jjvm.rtda.obj.JObject;
import com.hqd.jjvm.rtda.obj.JString;
import com.hqd.jjvm.rtda.slot.RefSlot;
import com.hqd.jjvm.util.ClassUtil;
import lombok.Getter;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

@Getter
public class JClassNativeRegistry extends JNativeRegistry {
    private static final JClassNativeRegistry instance = new JClassNativeRegistry();
    protected static final String CLASS_PATH = "java/lang/Class";

    static {
        registry(CLASS_PATH, "registerNatives", "()V", JNativeRegistry::registerNatives);
        registry(CLASS_PATH, "getPrimitiveClass", "(Ljava/lang/String;)Ljava/lang/Class;", JClassNativeRegistry::getPrimitiveClass);
        registry(CLASS_PATH, "getName0", "()Ljava/lang/String;", JClassNativeRegistry::getName0);
        registry(CLASS_PATH, "desiredAssertionStatus0", "(Ljava/lang/Class;)Z", JClassNativeRegistry::desiredAssertionStatus0);
        registry(CLASS_PATH, "isInterface", "()Z", JClassNativeRegistry::isInterface);
        registry(CLASS_PATH, "isPrimitive", "()Z", JNativeRegistry::isPrimitive);
        registry(CLASS_PATH, "getDeclaredFields0", "(Z)[Ljava/lang/reflect/Field;", JClassNativeRegistry::getDeclaredFields0);
        registry(CLASS_PATH, "getModifiers", "()I", JClassNativeRegistry::getModifiers);
        registry(CLASS_PATH, "getSuperclass", "()Ljava/lang/Class;", JClassNativeRegistry::getSuperclass);
        registry(CLASS_PATH, "forName0", "(Ljava/lang/String;ZLjava/lang/ClassLoader;Ljava/lang/Class;)Ljava/lang/Class;", JClassNativeRegistry::forName0);
        registry(CLASS_PATH, "getDeclaredConstructors0", "(Z)[Ljava/lang/reflect/Constructor;", JClassNativeRegistry::getDeclaredConstructors0);
        registry(CLASS_PATH, "isAssignableFrom", "(Ljava/lang/Class;)Z", JClassNativeRegistry::isAssignableFrom);
        registry(CLASS_PATH, "getInterfaces0", "()[Ljava/lang/Class;", JClassNativeRegistry::getInterfaces0);
        registry(CLASS_PATH, "isArray", "()Z", JClassNativeRegistry::isArray);
        registry(CLASS_PATH, "getComponentType", "()Ljava/lang/Class;", JClassNativeRegistry::getComponentType);
    }

    private static void getComponentType(JThread jThread) {
        LocalVars vars = jThread.getJvmStack().getTop().getLocalVars();
        JObject ref = vars.getRefVal(0);
        JClass jc = (JClass) ref.getExtra();
        try {
            if (ClassUtil.isArrayClass(jc.getClassName())) {
                JClass componentJC = jc.getLoader().loadJClass(jc.getClassName().substring(2));
                jThread.getJvmStack().pushOperandStackRefVal(componentJC.getJObject());
            } else {
                jThread.getJvmStack().pushOperandStackRefVal(null);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void isArray(JThread jThread) {
        JClass jc = (JClass) jThread.getJvmStack().getLocalVarsRefVal(0).getExtra();
        jThread.getJvmStack().pushOperandStackBoolVal(jc.isArray());
    }

    private static void getInterfaces0(JThread jThread) {
        JClass jc = (JClass) jThread.getJvmStack().getLocalVarsRefVal(0).getExtra();
        jThread.getJvmStack().pushOperandStackBoolVal(jc.isInterface());
    }

    private static void getSuperclass(JThread jThread) {
        JClass jc = (JClass) jThread.getJvmStack().getLocalVarsRefVal(0).getExtra();
        if (jc.getSuperClass() != null)
            jThread.getJvmStack().pushOperandStackRefVal(jc.getSuperClass().getJObject());
        else {
            jThread.getJvmStack().pushOperandStackRefVal(null);
        }
    }

    private static void getModifiers(JThread jThread) {
        JClass jc = (JClass) jThread.getJvmStack().getLocalVarsRefVal(0).getExtra();
        jThread.getJvmStack().pushOperandStackIntVal(jc.getAccessFlags());
    }

    private static void getDeclaredConstructors0(JThread jThread) {
        LocalVars vars = jThread.getJvmStack().getTop().getLocalVars();
        JObject ref = vars.getRefVal(0);
        boolean isPublic = vars.getBoolVal(1);
        JClass jc = (JClass) ref.getExtra();
        List<JMethod> cs = new ArrayList<>();
        for (JMethod jm : jc.getJMethods()) {
            if (jm.getName().equals("<init>")) {
                if (!isPublic || jm.isPublic()) {
                    cs.add(jm);
                }
            }
        }
        try {
            JClass cc = jc.getLoader().loadJClass(Constructor.class.getName());
            JObject cArr = jc.getLoader().loadJClass(Constructor[].class.getName()).newJArray(cs.size());
            jThread.getJvmStack().pushOperandStackRefVal(cArr);
            if (cs.size() > 0) {
                JObject[] cos = (JObject[]) cArr.getData();
                JMethod initMethod = cc.getJMethod("<init>", "(Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Class;IILjava/lang/String;[B[B)V");
                for (int i = 0; i < cs.size(); i++) {
                    JMethod constructor = cs.get(i);
                    JObject co = cc.newJObject();
                    co.setExtra(constructor);
                    cos[i] = co;
                    jThread.getJvmStack().pushOperandStackRefVal(co);
                    jThread.getJvmStack().pushOperandStackRefVal(ref);
                    jThread.getJvmStack().pushOperandStackRefVal(new JObject(constructor.getJClass(), constructor.getParamType()));
                    jThread.getJvmStack().pushOperandStackRefVal(new JObject(constructor.getJClass(), constructor.getExceptionJClassObj()));
                    jThread.getJvmStack().pushOperandStackIntVal(constructor.getAccessFlags());
                    jThread.getJvmStack().pushOperandStackIntVal(0);//TODO
                    jThread.getJvmStack().pushOperandStackRefVal(JString.newJString(cc.getLoader(), constructor.getJSignature()));
                    jThread.getJvmStack().pushOperandStackRefVal(new JObject(cc.getLoader().loadJClass("[B"), constructor.getAnnoBytes()));
                    jThread.getJvmStack().pushOperandStackRefVal(new JObject(cc.getLoader().loadJClass("[B"), constructor.getParamAnnos()));
                    jThread.createStackFrame(initMethod);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void isInterface(JThread jThread) {
        JObject ref = jThread.getJvmStack().getTop().getLocalVars().getRefVal(0);
        JClass jc = (JClass) ref.getExtra();
        jThread.getJvmStack().pushOperandStackBoolVal(jc.isInterface());
    }

    private static void desiredAssertionStatus0(JThread jThread) {
        /**
         * TODO 从VM中检索该类所需的断言状态
         */
        jThread.getJvmStack().pushOperandStackBoolVal(false);
    }

    private static void forName0(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        JObject refVal = localVars.getRefVal(0);
        JString jName = JString.newJString(refVal);
        int initialize = localVars.getIntVal(1);
        try {
            JClass jc = jThread.getJvmStack().getTop().getJClass().getLoader().loadJClass(jName.getData());
            if (initialize == 1 && !jc.getState().equals(JClassState.FULLY_INITIALIZED)) {
                jThread.getJvmStack().getTop().setReturnAddress(jThread.getNextPc());
                //执行类初始化方法
                Deque<JMethod> jMethods = jc.initClinit();
                if (jMethods.isEmpty()) {
                    jThread.getJvmStack().pushOperandStackRefVal(jc.getJObject());
                } else {
                    while (!jMethods.isEmpty()) {
                        JMethod clinitMethod = jMethods.poll();
                        jThread.createStackFrame(new JMethodRef(clinitMethod.getJClass(), clinitMethod).getJMethod());
                    }
                }
            } else {
                jThread.getJvmStack().pushOperandStackRefVal(jc.getJObject());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected static void getName0(JThread jThread) {
        JObject thisRef = jThread.getJvmStack().getLocalVarsRefVal(0);
        JClass jc = thisRef.getJClass();
        String javaName = jc.getClassName().replaceAll("/", "\\.");
        JString str = jc.getConstantPool().getStringPool().internString(jc.getLoader(), javaName);
        jThread.getJvmStack().pushOperandStackRefVal(str);

    }

    protected static void getPrimitiveClass(JThread jThread) {
        try {
            StackFrame stackFrame = jThread.getJvmStack().getTop();
            JObject jObject = ((RefSlot) stackFrame.getLocalVarsVal(0)).getVal();
            RefSlot refSlot = jObject.getField(0);
            String className = new String((char[]) refSlot.getVal().getData());
            JClassLoader classLoader = stackFrame.getJClass().getLoader();
            JClass jClass = classLoader.loadJClass(className);
            stackFrame.pushRef(jClass.getJObject());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected static void getDeclaredFields0(JThread jThread) {
        LocalVars localVars = jThread.getJvmStack().getTop().getLocalVars();
        JObject thisObj = localVars.getRefVal(0);
        Integer publicOnly = localVars.getIntVal(1);
        JClass jc = (JClass) thisObj.getExtra();
        List<JField> jFields = new ArrayList<>();
        if (publicOnly == 1) {
            for (JField jf : jc.getJFields()) {
                if (jf.isPublic()) {
                    jFields.add(jf);
                }
            }
        } else {
            jFields.addAll(Arrays.asList(jc.getJFields()));
        }
        JClassLoader loader = jThread.getJvmStack().getTop().getJClass().getLoader();
        try {
            JClass jfiedClass = loader.loadJClass(Field.class.getName());
            JObject jfArr = loader.loadJClass("[L" + jfiedClass.getClassName() + ";").newJArray(jFields.size());
            jThread.getJvmStack().pushOperandStackRefVal(jfArr);
            if (jFields.size() > 0) {
                JObject[] fieldObjs = (JObject[]) jfArr.getData();
                JMethod jFieldInitMethod = jfiedClass.getJMethod("<init>", "(Ljava/lang/Class;" +
                        "Ljava/lang/String;" +
                        "Ljava/lang/Class;" +
                        "II" +
                        "Ljava/lang/String;" +
                        "[B)V");
                for (int i = 0; i < jFields.size(); i++) {
                    JObject fieldObj = jfiedClass.newJObject();
                    fieldObjs[i] = fieldObj;

                    jThread.getJvmStack().pushOperandStackRefVal(fieldObj);
                    jThread.getJvmStack().pushOperandStackRefVal(thisObj);

                    JString str = jc.getConstantPool().getStringPool().internString(jc.getLoader(), jFields.get(i).getName());//JString.newJString(jc.getLoader(), jFields.get(i).getName());
                    jThread.getJvmStack().pushOperandStackRefVal(str);
                    jThread.getJvmStack().pushOperandStackRefVal(ClassUtil.getJClassObj(jFields.get(i).getDescriptor(), loader));
                    jThread.getJvmStack().pushOperandStackIntVal(jFields.get(i).getAccessFlags());
                    jThread.getJvmStack().pushOperandStackIntVal(jFields.get(i).getSlotId());
                    //签名
                    JObject signature = JString.newJString(jc.getLoader(), null);
                    if (jFields.get(i).getJSignature() != null) {
                        signature = JString.newJString(jc.getLoader(), jFields.get(i).getJSignature().getSignature());
                    }
                    jThread.getJvmStack().pushOperandStackRefVal(signature);

                    int length = 0;
                    if (jFields.get(i).getAnnoBytes() != null) {
                        length = jFields.get(i).getAnnoBytes().length;
                    }
                    JObject byteArr = loader.loadJClass("[B").newJArray(length);
                    byteArr.setData(jFields.get(i).getAnnoBytes());
                    jThread.getJvmStack().pushOperandStackRefVal(byteArr);
                    jThread.createStackFrame(jFieldInitMethod);
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static JClassNativeRegistry getInstance() {
        return instance;
    }
}
